/**
 * Copyright 2010 Adam Ruggles.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package saig.engine;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.util.ArrayList;
import java.util.List;

import saig.engine.models.Spawn;
import saig.engine.models.Tile;


/**
 * Represents a game level.
 * @author Adam
 * @version $Id$
 * 
 * Created on Jul 6, 2010 at 4:24:14 PM 
 */
public class Level implements Cloneable {
	/**
	 * Spawn regions. 
	 */
	public static final int SPAWN_REGIONS = 10;
	/**
	 * The finish location for the level.
	 */
	private Point end;
	/**
	 * The height of the tile map.
	 */
	private final int height;
	/**
	 * Used to store the height of the level in pixels (vs tiles).
	 */
	private final int pixelHeight;
	/**
	 * Used to store the width of the level in pixels (vs tiles).
	 */
	private final int pixelWidth;
	/**
	 * The size in pixels for a spawn index region.
	 */
	private final int regionSize;
	/**
	 * Whether or not to render the grid when rendering the level.
	 */
	private boolean showGrid = false;
	/**
	 * The enemy spawn mapping.
	 */
	private final List<Spawn>[] spawns;
	/**
	 * The starting point for the player.
	 */
	private Point start;
	/**
	 * The height of a tile in pixels.
	 */
	private final int tileHeight;
	/**
	 * The tile mapping.
	 */
	private final Tile[][] tileMap;
	/**
	 * The width of a tile in pixels.
	 */
	private final int tileWidth;
	/**
	 * The width of the tile map.
	 */
	private final int width;
	/**
	 * Constructs a Level.
	 * @param width The level width (number of tiles across).
	 * @param height The level height (number of tiles up/down).
	 * @param tileWidth The height of a tile in pixels.
	 * @param tileHeight The height of a tile in pixels.
	 */
	@SuppressWarnings("unchecked")
	public Level(final int width, final int height, final int tileWidth, final int tileHeight) {
		this.tileMap = new Tile[width][height];
		this.height = height;
		this.width = width;
		this.tileHeight = tileHeight;
		this.tileWidth = tileWidth;
		this.pixelHeight = height * tileHeight;
		this.pixelWidth = width * tileWidth;
		this.spawns = (List<Spawn>[]) new ArrayList[SPAWN_REGIONS];
		this.regionSize = getPixelWidth() / SPAWN_REGIONS;
		this.start = null;
		this.end = null;
	}
	/**
	 * {@inheritDoc}
	 * @see java.lang.Object#clone()
	 */
	@Override
	public Object clone() throws CloneNotSupportedException {
		super.clone();
		Level copyLevel = new Level(width, height, tileWidth, tileHeight);
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				copyLevel.setTile(x, y, tileMap[x][y]);
			}
		}
		for (int r = 0; r < SPAWN_REGIONS; r++) {
			if (spawns[r] != null) {
				for (Spawn spawn : spawns[r]) {
					copyLevel.setSpawn(new Spawn(spawn));
				}
			}
		}
		copyLevel.setShowGrid(showGrid);
		return copyLevel;
	}
	/**
	 * Renders the current level at a given view position.
	 * @param g The graphics object to render to.
	 * @param x The left x position of the view.
	 * @param y The top y position of the view.
	 * @param width The width of the view.
	 * @param height The height of the view.
	 */
	public void doRender(Graphics g, final double x, final double y, final double width, final double height) {
		final int startX = (int) x / tileWidth;
		final int endX = (int) Math.min(Math.ceil((x + width) / tileWidth), this.width);
		final int startY = (int) y / tileHeight;
		final int endY = (int) Math.min(Math.ceil((y + height) / tileHeight), this.height);
		for (int xpos = startX; xpos < endX; xpos++) {
			int drawX = xpos * tileWidth - (int) x;
			for (int ypos = startY; ypos < endY; ypos++) {
				if (tileMap[xpos][ypos] != null) {
					int drawY = ypos * tileHeight - (int) y;
					g.drawImage(tileMap[xpos][ypos].getImage(), drawX, drawY, null);
				}
			}
		}
		if (showGrid) {
			g.setColor(Color.gray);
			final int maxX = getPixelWidth();
			final int maxY = getPixelHeight();
			for(int xpos=0; xpos <= maxX; xpos += tileWidth) {
				g.drawLine(xpos, 0, xpos, maxY);
			}
			for(int ypos=0; ypos <= maxY; ypos += tileHeight) {
				g.drawLine(0, ypos, maxX, ypos);
			}
		}
	}
	/**
	 * Returns end.
	 * @return the end.
	 */
	public Point getEnd() {
		return end;
	}
	/**
	 * Returns height.
	 * @return the height.
	 */
	public int getHeight() {
		return height;
	}
	/**
	 * Returns the level height in pixels. 
	 * @return The level height in pixels. 
	 */
	public int getPixelHeight() {
		return pixelHeight;
	}
	/**
	 * Returns the level width in pixels.
	 * @return The level width in pixels.
	 */
	public int getPixelWidth() {
		return pixelWidth;
	}
	/**
	 * Returns regionSize.
	 * @return the regionSize.
	 */
	public int getRegionSize() {
		return regionSize;
	}
	/**
	 * Returns the spawn at a given position (can be null).
	 * @param x The x position (0 .. width).
	 * @param y The y position (0 .. height).
	 * @return A Spawn or null.
	 */
	public Spawn getSpawn(int x, int y) {
		if (x < 0 || y < 0 || x >= width || y >= height) {
			return null;
		}
		final int region = x / regionSize;
		if (spawns[region] != null) {
			for (Spawn spawn : spawns[region]) {
				if (spawn.getX() == x && spawn.getY() == y) {
					return spawn;
				}
			}
		}
		return null;
	}
	/**
	 * Returns a list of spawns within the region of the x value.
	 * A region is a division of the level width * tileWidth / number of regions.
	 * @param x The x value in the level to search.
	 * @return A list of <code>saig.engine.models.Spawn</code>s.
	 */
	public List<Spawn> getSpawns(int x) {
		final int region = x / regionSize;
		if (region < 0 || region >= SPAWN_REGIONS || spawns[region] == null) {
			return new ArrayList<Spawn>();
		}
		return spawns[region];
	}
	/**
	 * Returns start.
	 * @return the start.
	 */
	public Point getStart() {
		return start;
	}
	/**
	 * Returns the tile at a given position (can be null).
	 * @param x The x position (0 .. width).
	 * @param y The y position (0 .. height).
	 * @return A Tile or null.
	 */
	public Tile getTile(int x, int y) {
		if (x < 0 || y < 0 || x >= width || y >= height) {
			return null;
		}
		return tileMap[x][y];
	}
	/**
	 * Returns the height of a tile in pixels.
	 * @return the height of a tile in pixels.
	 */
	public int getTileHeight() {
		return tileHeight;
	}
	/**
	 * Returns the width of a tile in pixels.
	 * @return the width of a tile in pixels.
	 */
	public int getTileWidth() {
		return tileWidth;
	}
	/**
	 * Returns the number of tiles along the width.
	 * @return The number of tiles along the width.
	 */
	public int getWidth() {
		return width;
	}
	/**
	 * Initializes all the tiles to a default tile.
	 * @param tile The default tile.
	 */
	public void init(final Tile tile) {
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				tileMap[x][y] = tile;
			}
		}
	}
	/**
	 * Returns showGrid.
	 * @return the showGrid.
	 */
	public boolean isShowGrid() {
		return showGrid;
	}
	/**
	 * Sets end.
	 * @param end the end to set.
	 */
	public void setEnd(Point end) {
		this.end = end;
	}
	/**
	 * Sets showGrid.
	 * @param showGrid the showGrid to set.
	 */
	public void setShowGrid(boolean showGrid) {
		this.showGrid = showGrid;
	}
	/**
	 * Sets a spawn on the level's spawn map.
	 * @param x The x position.
	 * @param y The y position.
	 * @param spawn The spawn to set.
	 */
	public void setSpawn(final Spawn spawn) {
		if (spawn != null && spawn.getX() >= 0 && spawn.getY() >= 0
				&& spawn.getX() < getPixelWidth() && spawn.getY() < getPixelHeight()) {
			final int region = spawn.getX() / (getPixelWidth() / SPAWN_REGIONS);
			if (spawns[region] == null) {
				spawns[region] = new ArrayList<Spawn>();
				spawns[region].add(spawn);
			} else {
				spawns[region].add(spawn);
			}
		}
	}
	public void removeSpawns(final int x1, final int y1, final int x2, final int y2) {
		final int region1 = x1 / (getPixelWidth() / SPAWN_REGIONS);
		final int region2 = x2 / (getPixelWidth() / SPAWN_REGIONS);
		for (int i = region1; i <= region2; i++) {
			List<Spawn> toRemove = new ArrayList<Spawn>();
			if (spawns[i] != null) {
				for (Spawn spawn : spawns[i]) {
					if (spawn.getX() >= x1 && spawn.getX() <= x2
							&& spawn.getY() >= y1 && spawn.getY() <= y2) {
						toRemove.add(spawn);
					}
				}
				spawns[i].removeAll(toRemove);
			}
		}
	}
	/**
	 * Sets start.
	 * @param start the start to set.
	 */
	public void setStart(Point start) {
		this.start = start;
	}
	/**
	 * Sets a tile on the level's tile map.
	 * @param x The x position.
	 * @param y The y position.
	 * @param tile The tile to set.
	 */
	public void setTile(final int x, final int y, final Tile tile) {
		if (tile != null && x >= 0 && y >= 0 && x < width && y < height) {
			tileMap[x][y] = tile;
		}
	}
}
